/* **********************************************************
 * Copyright 2010 Dimitar Vangelovski
 *
 * This file is part of the Gens3 3D Engine.
 *
 * The Gens3 3D Engine is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * The Gens3 3D Engine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Gens3 3D Engine; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA
 * **********************************************************/


#include "common.h"
#include "App.h"
#include "ImageGL.h"
#include "Material.h"
#include "SubMaterial.h"
#include "MapDiffuse.h"
#include "Scene.h"

#define TGA_RGB			2		// A normal RGB TGA file
#define TGA_ALPHA		3		// An ALPHA TGA file
#define TGA_RLE			10		// Run-Length Encoded (RLE) TGA file
#define MAX_TEXTURES		10000

unsigned int			g_iTexture[MAX_TEXTURES];	// a global variable to hold texture indexes for binded OpenGL textures


CImageGL::CImageGL()
: m_iChannels(0)
, m_iWidth(0)
, m_iHeight(0)
, m_pImageBuf(NULL)
{
}

CImageGL::~CImageGL()
{
}

void CImageGL::LoadTGA(const char * cFilename)
{
	unsigned short sWidth = 0;
	unsigned short sHeight = 0;
	unsigned char cLength = 0;
	unsigned char cImageType = 0;
	unsigned char cBits = 0;

	const char * cFilenameDefault = "textures/nofile.tga";

	FILE *pFile = NULL;
	int iChannels = 0;
	int iStride = 0;
	int i = 0;

	// open the texture file
	//if (fopen_s(&pFile, cFilename, "rbS") != 0)
	//std::cout << "LoadTGA() Opening file: " << cFilename << std::endl;
	pFile = fopen(cFilename, "rbS");

	if (!pFile)
	{
		std::string strTemp = "Unable to load TGA file: ";
		strTemp += std::string(cFilename);
		strTemp += "\n\nWill try to load the default texture nofile.tga instead...";

                CApp::WriteToLog(strTemp.c_str());
		//std::cout << strTemp << std::endl;
		//::MessageBox(NULL, s2ws(strTemp).c_str(), _T("Error opening file"), MB_OK | MB_ICONERROR);

		// try to open the default texture
		//if (fopen_s(&pFile, cFilenameDefault, "rbS") != 0)
		//std::cout << "LoadTGA() Opening file: " << cFilenameDefault << std::endl;
		pFile = fopen(cFilenameDefault, "rbS");

		if (!pFile)
		{
                        CApp::WriteToLog("Unable to open default texture nofile.tga!\n\nExiting Gens3...");
			//::MessageBox(NULL, _T("Unable to open default texture nofile.tga!\n\nExiting Gens3..."), _T("Error opening file"), MB_OK | MB_ICONERROR);
			exit(1);
		}
	}

	char buf[1024];
	setvbuf(pFile, buf, _IOFBF, sizeof(buf));

	try
	{
		// Read in the length in bytes from the header to the pixel data
		fread(&cLength, sizeof(unsigned char), 1, pFile);

		// Jump over one byte
		fseek(pFile, 1, SEEK_CUR);

		// Read in the imageType (RLE, RGB, etc...)
		fread(&cImageType, sizeof(unsigned char), 1, pFile);

		// Skip the data we don't care about
		fseek(pFile, 9, SEEK_CUR);

		// Read the width, height and bits per pixel (16, 24 or 32)
		fread(&sWidth,  sizeof(unsigned short), 1, pFile);
		fread(&sHeight, sizeof(unsigned short), 1, pFile);
		fread(&cBits,   sizeof(unsigned char), 1, pFile);

		// Now we move the file pointer to the pixel data
		fseek(pFile, cLength + 1, SEEK_CUR);

		// Check if the image is RLE compressed or not
		if (cImageType != TGA_RLE)
		{
			m_pImageBuf = NULL;

			try
			{
				// Check if the image is a 24 or 32-bit image
				if (cBits == 24 || cBits == 32)
				{
					// Calculate the channels (3 or 4) - (use bits >> 3 for more speed).
					// Next, we calculate the stride and allocate enough memory for the pixels.
					iChannels = cBits / 8;
					iStride = iChannels * sWidth;

					// Allocate the memory for the image
					m_pImageBuf = new unsigned char[iStride * sHeight];

					// Load in all the pixel data line by line
					for (int y = 0; y < sHeight; y++)
					{
						// Store a pointer to the current line of pixels
						unsigned char *pLine = &(m_pImageBuf[iStride * y]);

						// Read in the current line of pixels
						fread(pLine, iStride, 1, pFile);

						// Go through all of the pixels and swap the B and R values since TGA
						// files are stored as BGR instead of RGB (or use GL_BGR_EXT verses GL_RGB)
						int iTemp = 0;
						for (i = 0; i < iStride; i += iChannels)
						{
							iTemp = pLine[i];
							pLine[i] = pLine[i + 2];
							pLine[i + 2] = iTemp;
						}
					}
				}
				// Check if the image is a 16 bit image (RGB stored in 1 unsigned short)
				else if (cBits == 16)
				{
					unsigned short sPixels = 0;
					int r = 0, g = 0, b = 0;

					// Since we convert 16-bit images to 24 bit, we hardcode the channels to 3.
					// We then calculate the stride and allocate memory for the pixels.
					iChannels = 3;
					iStride = iChannels * sWidth;
					m_pImageBuf = new unsigned char[iStride * sHeight];

					// Load in all the pixel data pixel by pixel
					for (i = 0; i < sWidth * sHeight; i++)
					{
						// Read in the current pixel
						fread(&sPixels, sizeof(unsigned short), 1, pFile);

						// To convert a 16-bit pixel into an R, G, B, we need to
						// do some masking and such to isolate each color value.
						// 0x1f = 11111 in binary, so since 5 bits are reserved in
						// each unsigned short for the R, G and B, we bit shift and mask
						// to find each value.  We then bit shift up by 3 to get the full color.
						b = (sPixels & 0x1f) << 3;
						g = ((sPixels >> 5) & 0x1f) << 3;
						r = ((sPixels >> 10) & 0x1f) << 3;

						// This essentially assigns the color to our array and swaps the
						// B and R values at the same time.
						m_pImageBuf[i * 3 + 0] = r;
						m_pImageBuf[i * 3 + 1] = g;
						m_pImageBuf[i * 3 + 2] = b;
					}
				}
				// Else return because we have a bad or unsupported pixel format
				else
				{
					return;
				}
			}
			catch (std::exception& e)
			{
                                CApp::WriteToLog(e.what());
				delete[] m_pImageBuf;
			}
		}
		// Else, it must be Run-Length Encoded (RLE)
		else
		{
			unsigned char rleID = 0;
			int iColorsRead = 0;
			iChannels = cBits / 8;
			iStride = iChannels * sWidth;
			m_pImageBuf = NULL;

			try
			{
				m_pImageBuf = new unsigned char[iStride * sHeight];

				unsigned char * pColors = NULL;
				pColors = new unsigned char[iChannels];

				// Load in all the pixel data
				while (i < sWidth * sHeight)
				{
					// Read in the current color count + 1
					fread(&rleID, sizeof(unsigned char), 1, pFile);

					// Check if we don't have an encoded std::string of colors
					if (rleID < 128)
					{
						// Increase the count by 1
						rleID++;

						// Go through and read all the unique colors found
						while (rleID)
						{
							// Read in the current color
							fread(pColors, sizeof(unsigned char) * iChannels, 1, pFile);

							// Store the current pixel in our image array
							m_pImageBuf[iColorsRead + 0] = pColors[2];
							m_pImageBuf[iColorsRead + 1] = pColors[1];
							m_pImageBuf[iColorsRead + 2] = pColors[0];

							// If we have a 4 channel 32-bit image, assign one more for the alpha
							if (cBits == 32)
							{
								m_pImageBuf[iColorsRead + 3] = pColors[3];
							}

							// Increase the current pixels read, decrease the amount
							// of pixels left, and increase the starting index for the next pixel.
							i++;
							rleID--;
							iColorsRead += iChannels;
						}
					}
					// Else, let's read in a std::string of the same character
					else
					{
						// Minus the 128 ID + 1 (127) to get the color count that needs to be read
						rleID -= 127;

						// Read in the current color, which is the same for a while
						fread(pColors, sizeof(unsigned char) * iChannels, 1, pFile);

						// Go and read as many pixels as are the same
						while (rleID)
						{
							// Assign the current pixel to the current index in our pixel array
							m_pImageBuf[iColorsRead + 0] = pColors[2];
							m_pImageBuf[iColorsRead + 1] = pColors[1];
							m_pImageBuf[iColorsRead + 2] = pColors[0];

							// If we have a 4 channel 32-bit image, assign one more for the alpha
							if (cBits == 32)
							{
								m_pImageBuf[iColorsRead + 3] = pColors[3];
							}

							// Increase the current pixels read, decrease the amount
							// of pixels left, and increase the starting index for the next pixel.
							i++;
							rleID--;
							iColorsRead += iChannels;
						}
					}
				}

				delete[] pColors;
			}
			catch (std::exception& e)
			{
                                CApp::WriteToLog(e.what());
				delete[] m_pImageBuf;
			}
		}

		m_iChannels = iChannels;
		m_iWidth = sWidth;
		m_iHeight = sHeight;
	}
	catch (std::exception& e)
	{
                CApp::WriteToLog(e.what());
		delete[] m_pImageBuf;
	}

	fclose(pFile);
}

void CImageGL::CreateTexture(unsigned int textureArray[], const char * strFileName, int iTextureID, bool &bHasAlpha)
{
	if (!strFileName)
	{
		return;
	}

	bool bAlpha = false;

	//std::cout << "CreateTexture() Opening file: " << strFileName << std::endl;

	// Load the TGA image given in the strFileName
	LoadTGA(strFileName);

	// If the image is not loaded => exit
	if (m_pImageBuf == NULL)
	{
		exit(0);
	}

	// Generate a texture with the associative texture ID stored in the array
	glGenTextures(1, &textureArray[iTextureID]);

	// Bind the texture to the texture arrays index and init the texture
	glBindTexture(GL_TEXTURE_2D, textureArray[iTextureID]);

	// Assume that the texture is a 24 bit RGB texture (We convert 16-bit ones to 24-bit)
	int iTextureType = GL_RGB;

	// If the image is 32-bit (4 channels), then we need to specify GL_RGBA for an alpha channel
	if (m_iChannels == 4)
	{
		iTextureType = GL_RGBA;
		bAlpha =  true;
	}

	// Build Mipmaps (builds different versions of the picture for distances - looks better)
	gluBuild2DMipmaps(GL_TEXTURE_2D, m_iChannels, m_iWidth, m_iHeight, iTextureType, GL_UNSIGNED_BYTE, m_pImageBuf);

	// Set GL_LINEAR_MIPMAP_LINEAR (Trilinear) texture filtering
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);

	// Free the image buffer
	if (m_pImageBuf)
	{
		delete[] m_pImageBuf;
	}

	bHasAlpha = bAlpha;
}

bool CImageGL::LoadGLTextures(CScene * pScene)
{
	std::string strTextureNames[MAX_TEXTURES];
	bool bTextureIsTransparent[MAX_TEXTURES];

	int iIndex = 0;
	int iTextureCount = 0;
	bool bTextureExists = false;
	bool bHasAlpha = false;
	bool bTransparency = false;
	std::string strName, strTemp;

	// Add a default texture in case some polygon has no material defined (for debugging purposes)
	CreateTexture(g_iTexture, "textures/nofile.tga", 0, bHasAlpha);
	strTextureNames[0] = "nofile";
	bTextureIsTransparent[0] = bHasAlpha;
	iTextureCount++;

	/////////////////////////////////////////////////////////////////////////////////////////////
	// Load all materials

	//std::cout << "LoadGLTextures() material count: " << pScene->GetMaterialCount() << std::endl;

        try
        {
                for (int iMatCount = 0; iMatCount < pScene->GetMaterialCount(); iMatCount++)
                {
                        // Check if a texture with this name has already been loaded
                        bTextureExists = false;
                        for (iIndex = 0; iIndex < iTextureCount; iIndex++)
                        {
                                if (strTextureNames[iIndex].compare(pScene->GetMaterial(iMatCount)->GetMapDiffuse()->GetBitmap()) == 0)
                                {
                                        bTextureExists = true;
                                        break;
                                };
                        };

                        if (!bTextureExists)
                        {
                                strName = pScene->GetMaterial(iMatCount)->GetMapDiffuse()->GetBitmap();

                                //std::cout << "LoadGLTextures() GetBitmap(): " << pScene->GetMaterial(iMatCount)->GetMapDiffuse()->GetBitmap() << std::endl;
                                //std::cout << "LoadGLTextures() strName: " << strName << std::endl;

                                strTemp = "textures/";
                                strTemp += strName;
                                CreateTexture(g_iTexture, strTemp.c_str(), iIndex, bHasAlpha);

                                strTextureNames[iIndex] = strName;
                                pScene->GetMaterial(iMatCount)->GetMapDiffuse()->SetIndex(iIndex);
                                bTransparency = bHasAlpha || pScene->GetMaterial(iMatCount)->GetMaterialTransparency() > 0.01f;
                                pScene->GetMaterial(iMatCount)->GetMapDiffuse()->SetTransparent(bTransparency);
                                bTextureIsTransparent[iIndex] = bTransparency;
                                iTextureCount++;
                        }
                        else
                        {
                                pScene->GetMaterial(iMatCount)->GetMapDiffuse()->SetIndex(iIndex);
                                pScene->GetMaterial(iMatCount)->GetMapDiffuse()->SetTransparent(bTextureIsTransparent[iIndex]);
                        }

                        ////////////////////////////////////////////////////////////////////////////////////////
                        // Load all submaterials

                        for (int iSubMatCount = 0; iSubMatCount < pScene->GetMaterial(iMatCount)->GetNumSubMtls(); iSubMatCount++)
                        {
                                // check if a texture with this name has already been loaded
                                bTextureExists = false;
                                for (iIndex = 0; iIndex < iTextureCount; iIndex++)
                                {
                                        if (strTextureNames[iIndex].compare(pScene->GetMaterial(iMatCount)->GetSubMaterial(iSubMatCount)->GetMapDiffuse()->GetBitmap()) == 0)
                                        {
                                                bTextureExists = true;
                                                break;
                                        };
                                };

                                if (!bTextureExists)
                                {
                                        strName = pScene->GetMaterial(iMatCount)->GetSubMaterial(iSubMatCount)->GetMapDiffuse()->GetBitmap();
                                        strTemp = "textures/";
                                        strTemp += strName;
                                        CreateTexture(g_iTexture, strTemp.c_str(), iIndex, bHasAlpha);

                                        strTextureNames[iIndex] = strName;
                                        pScene->GetMaterial(iMatCount)->GetSubMaterial(iSubMatCount)->GetMapDiffuse()->SetIndex(iIndex);
                                        bTransparency = bHasAlpha || pScene->GetMaterial(iMatCount)->GetSubMaterial(iSubMatCount)->GetMaterialTransparency() > 0.01f;
                                        pScene->GetMaterial(iMatCount)->GetSubMaterial(iSubMatCount)->GetMapDiffuse()->SetTransparent(bTransparency);
                                        bTextureIsTransparent[iIndex] = bTransparency;
                                        iTextureCount++;
                                }
                                else
                                {
                                        pScene->GetMaterial(iMatCount)->GetSubMaterial(iSubMatCount)->GetMapDiffuse()->SetIndex(iIndex);
                                        pScene->GetMaterial(iMatCount)->GetSubMaterial(iSubMatCount)->GetMapDiffuse()->SetTransparent(bTextureIsTransparent[iIndex]);
                                }
                        }
                }
        }
        catch (std::exception& e)
        {
                CApp::WriteToLog(e.what());
                return false;
        }

	return true;
}
